package aNN;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;


/*
 Layer 0 (Input)
841 neurons

Layer 1
1014 neurons
156 weights
26364 connections

Layer 2
1250 neurons
7800 weights
188750 connections

Layer 3
100 neurons
125100 weights
125100 connections

Layer 4
10 neurons
1010 weights
1010 connections
 */
public class NeuralNetwork {

	public List<Layer> Layers;
	public Layer InputLayer;
	public Layer OutputLayer;
	private Random randomGen = new Random();
	
	public void Calculate()
	{
		for (int i = 1; i < Layers.size(); i++)
		{
			Layers.get(i).Calculate();
		}
	}
	
	public NeuronOutputPair[] TempCalculate(HashMap<Neuron, NeuronOutputPair> inputLayer)
	{
		int size = 0;
		for (int i = 0; i < Layers.size(); i++)
		{
			size += Layers.get(i).Neurons.size();
		}
		NeuronOutputPair[] returnVal = new NeuronOutputPair[size];
		int tempIndex = 0;
		HashMap<Neuron, NeuronOutputPair> tempReturn = inputLayer;
		for (int i = 0; i < InputLayer.Neurons.size(); i++)
		{
			Neuron neuron = InputLayer.Neurons.get(i);
			returnVal[i] = inputLayer.get(neuron);
		}
		tempIndex += InputLayer.Neurons.size();
		for (int i = 1; i < Layers.size(); i++)
		{
			Layer layer = Layers.get(i);
			tempReturn = layer.TempCalculate(tempReturn);
			for (int j = 0; j < layer.Neurons.size(); j++)
			{
				returnVal[tempIndex + j] = tempReturn.get(layer.Neurons.get(j));
			}
			tempIndex += Layers.get(i).Neurons.size();
		}
		return returnVal;
	}
	
	public void LoadTempCalculate(NeuronOutputPair[] neuronValues)
	{
		int index = 0;
		for (int i = 0; i < Layers.size(); i++)
		{
			Layer layer = Layers.get(i);
			for (int j = 0; j < layer.Neurons.size(); j++)
			{
				Neuron neuron = layer.Neurons.get(j);
				neuron.setValues(neuronValues[index + j].CurrentValue, neuronValues[index + j].OutputValue);
			}
			index += layer.Neurons.size();
		}
	}
	
	public List<HashMap<Weight, RefDouble>> BackPropagate(double eta, double dMicron, double[] desiredOutput, boolean adjustWeights)
	{
//		Caculate();
		HashMap<Neuron, RefDouble> targets = new HashMap<Neuron, RefDouble>();
		for (int i = 0; i < OutputLayer.Neurons.size(); i++)
		{
			targets.put(OutputLayer.Neurons.get(i), new RefDouble(desiredOutput[i]));
		}
		ErrorComputeOutLayer ecOuter = new ErrorComputeOutLayer(targets);
		List<HashMap<Weight, RefDouble>> weightsToAdjust = new ArrayList<HashMap<Weight,RefDouble>>(OutputLayer.Neurons.size() - 1);
		int numWeightList = 0;
		weightsToAdjust.add(new HashMap<Weight, RefDouble>());
		targets = OutputLayer.BackPropagate(eta, dMicron, ecOuter, weightsToAdjust.get(numWeightList++));
		for (int i = Layers.size() - 2; i > 0; i--)
		{
			weightsToAdjust.add(new HashMap<Weight, RefDouble>());
			ErrorComputeInnerLayer ecInner = new ErrorComputeInnerLayer(targets);
			targets = Layers.get(i).BackPropagate(eta, dMicron, ecInner, weightsToAdjust.get(numWeightList++));
		}
		targets.clear();
		if (adjustWeights == true)
		{
			for (int i = 0; i < weightsToAdjust.size(); i++)
			{
				Iterator<Entry<Weight, RefDouble>> weightIterator = weightsToAdjust.get(i).entrySet().iterator();
				while (weightIterator.hasNext())
				{
					Entry<Weight, RefDouble> pairs = weightIterator.next();
					Weight weight = pairs.getKey();
					RefDouble value = pairs.getValue();
					weight.setWeight(weight.getWeight() - (eta /* (weight.getDiagHessian() + dMicron)*/ * value.value /*/ value.Count */));
				}
			}
		}
		return weightsToAdjust;
	}
	
	public void BackPropagateAdjustWeights(double eta, List<HashMap<Weight,RefDouble>> weightsToAdjust)
	{
		for (int i = 0; i < weightsToAdjust.size(); i++)
		{
			Iterator<Entry<Weight, RefDouble>> weightIterator = weightsToAdjust.get(i).entrySet().iterator();
			while (weightIterator.hasNext())
			{
				Entry<Weight, RefDouble> pairs = weightIterator.next();
				Weight weight = pairs.getKey();
				RefDouble value = pairs.getValue();
				weight.setWeight(weight.getWeight() - (eta /* (weight.getDiagHessian() + dMicron)*/ * value.value /*/ value.Count */));
			}
		}
	}
	
	public void SecondOrderBackPropagate(double eta)
	{
		HashMap<Neuron, RefDouble> targets = new HashMap<Neuron, RefDouble>();
		for (int i = 0; i < OutputLayer.Neurons.size(); i++)
		{
			targets.put(OutputLayer.Neurons.get(i), new RefDouble(0));
		}
		ErrorComputeOutLayer ecOuter = new ErrorComputeOutLayer(targets);
		targets = OutputLayer.SecondOrderBackPropagate(eta, ecOuter);
		for (int i = Layers.size() - 2; i > -1; i--)
		{
			ErrorComputeInnerLayer ecInner = new ErrorComputeInnerLayer(targets);
			targets = Layers.get(i).SecondOrderBackPropagate(eta, ecInner);
		}
		targets.clear();
	}
	
	/*public void TestNeuralNetwork()
	{
		Layer inputLayer = new Layer();
		inputLayer.Neurons.add(new Neuron());
		inputLayer.Neurons.add(new Neuron());
		
		Layer layer1 = new Layer();
		layer1.Neurons.add(new Neuron());
		layer1.Neurons.add(new Neuron());
		layer1.Neurons.add(new Neuron());
		for (int i = 0; i < 2; i++)
		{
			for (int j = 0; j < 3; j++)
			{
				Connection con = new Connection(new Weight(randomGen.nextGaussian()), inputLayer.Neurons.get(i), layer1.Neurons.get(j));
				inputLayer.Neurons.get(i).OutputConnections.add(con);
				layer1.Neurons.get(j).InputConnections.add(con);
			}
		}
		for (int i = 0; i < layer1.Neurons.size(); i++)
		{
			Connection con = new Connection(new Weight(randomGen.nextGaussian()), new Neuron(1), layer1.Neurons.get(i));
			layer1.Neurons.get(i).InputConnections.add(con);
		}
		
		Layer outputLayer = new Layer();
		outputLayer.Neurons.add(new Neuron());
		for (int i = 0; i < 3; i++)
		{
			for (int j = 0; j < 1; j++)
			{
				Connection con = new Connection(new Weight(randomGen.nextGaussian()), layer1.Neurons.get(i), outputLayer.Neurons.get(j));
				layer1.Neurons.get(i).OutputConnections.add(con);
				outputLayer.Neurons.get(j).InputConnections.add(con);
			}
		}
		for (int i = 0; i < outputLayer.Neurons.size(); i++)
		{
			Connection con = new Connection(new Weight(randomGen.nextGaussian()), new Neuron(1), outputLayer.Neurons.get(i));
			outputLayer.Neurons.get(i).InputConnections.add(con);
		}
		
		Layers = new ArrayList<Layer>(3);
		
		Layers.add(inputLayer);
		Layers.add(layer1);
		Layers.add(outputLayer);
		this.InputLayer = inputLayer;
		this.OutputLayer = outputLayer;
	}
	
	
	
	public void ConvolutionNeuralNetwork()
	{
		Layer inputLayer = new Layer();
		Layer layer1 = new Layer();
		Layer layer2 = new Layer();
		Layer layer3 = new Layer();
		Layer outputLayer = new Layer();
		
		for (int i = 0; i < 29 * 29; i++)
		{
			Neuron newNeuron = new Neuron();
			inputLayer.Neurons.add(newNeuron);
		}

		//Layer 0 (input layer) to layer 1
		for (int i = 0; i < 6 * 13 * 13; i++)
		{
			Neuron newNeuron = new Neuron();
			layer1.Neurons.add(newNeuron);
		}
//		for (int i = 0; i < 841; i++)
//		{
//			for (int j = 0; j < 1014; j++)
//			{
//				Neuron inputNeuron = inputLayer.Neurons.get(i);
//				Neuron outputNeuron = layer1.Neurons.get(j);
//				Connection newConnection = new Connection(new Weight(randomGen.nextGaussian()), inputNeuron, outputNeuron);
//				inputNeuron.OutputConnections.add(newConnection);
//				outputNeuron.InputConnections.add(newConnection);
//			}
//		}
//		for (Neuron neuron : layer1.Neurons)
//		{
//			Weight newWeight = new Weight(randomGen.nextGaussian());
//			Neuron biasNeuron = new Neuron(1);
//			neuron.InputConnections.add(new Connection(newWeight, biasNeuron, neuron));
//		}
		List<Weight> layer1Weights = new ArrayList<Weight>();
		for (int i = 0; i < (5 * 5 + 1) * 6; i++)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / 26);
			layer1Weights.add(newWeight);
		}
		for (int i = 0; i < 6; i ++)
		{
			for (int j = 0; j < 13; j++)
			{
				for (int k = 0; k < 13; k++)
				{
					Neuron outputNeuron = layer1.Neurons.get(i * 13 * 13 + j * 13 + k);
					for (int l = 0; l < (5 * 5); l++)
					{
						Weight weight = layer1Weights.get(i * (5 * 5 + 1) + l);
						Neuron inputNeuron = inputLayer.Neurons.get((j * 2 + l / 5) * 29  + (k * 2 + l % 5));
						Connection newConnection = new Connection(weight, inputNeuron, outputNeuron);
						inputNeuron.OutputConnections.add(newConnection);
						outputNeuron.InputConnections.add(newConnection);
					}
					Weight biasWeight = layer1Weights.get(i * (5 * 5 + 1) + 25);
					Neuron biasNeuron = new Neuron(1); 
					outputNeuron.InputConnections.add(new Connection(biasWeight, biasNeuron, outputNeuron));
				}
			}
		}
		layer1Weights.clear();
		
		//Layer 1 to layer 2
		for (int i = 0; i < 50 * 5 * 5; i++)
		{
			Neuron newNeuron = new Neuron();
			layer2.Neurons.add(newNeuron);
		}
//		for (int i = 0; i < 1014; i++)
//		{
//			for (int j = 0; j < 1250; j++)
//			{
//				Neuron inputNeuron = layer1.Neurons.get(i);
//				Neuron outputNeuron = layer2.Neurons.get(j);
//				Connection newConnection = new Connection(new Weight(randomGen.nextGaussian()), inputNeuron, outputNeuron);
//				inputNeuron.OutputConnections.add(newConnection);
//				outputNeuron.InputConnections.add(newConnection);
//			}
//		}
//		for (Neuron neuron : layer2.Neurons)
//		{
//			Weight newWeight = new Weight(randomGen.nextGaussian());
//			Neuron biasNeuron = new Neuron(1);
//			neuron.InputConnections.add(new Connection(newWeight, biasNeuron, neuron));
//		}
		List<Weight> layer2Weights = new ArrayList<Weight>();
		for (int i = 0; i < (5 * 5 + 1) * 6 * 50; i++)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / (25 * 6 + 1));
			layer2Weights.add(newWeight);
		}
		for (int i = 0; i < 50; i++)
		{
			for (int j = 0; j < 5; j++)
			{
				for (int k = 0; k < 5; k++)
				{
					for (int l = 0; l < 6; l++)
					{
						Neuron outputNeuron = layer2.Neurons.get(i * 5 * 5 + j * 5 + k);
						for (int m = 0; m < (5 * 5); m++)
						{
							Weight weight = layer2Weights.get(i * (5 * 5 + 1) * 6 + l * (5 * 5 + 1) + m);
							Neuron inputNeuron = layer1.Neurons.get(l * 13 * 13 + (j * 2 + m / 5) * 13  + (k * 2 + m % 5));
							Connection newConnection = new Connection(weight, inputNeuron, outputNeuron);
							inputNeuron.OutputConnections.add(newConnection);
							outputNeuron.InputConnections.add(newConnection);
						}
						Weight biasWeight = layer2Weights.get(i * (5 * 5 + 1) * 6 + l * (5 * 5 + 1) + 25);
						Neuron biasNeuron = new Neuron(1); 
						outputNeuron.InputConnections.add(new Connection(biasWeight, biasNeuron, outputNeuron));
					}
				}
			}
		}
		layer2Weights.clear();
		
		//Layer 2 to layer 3
		for (int i = 0; i < 100; i++)
		{
			Neuron newNeuron = new Neuron();
			layer3.Neurons.add(newNeuron);
		}
		for (int i = 0; i < 100; i++)
		{
			for (int j = 0; j < 50 * 5 * 5; j++)
			{
				Neuron inputNeuron = layer2.Neurons.get(j);
				Neuron outputNeuron = layer3.Neurons.get(i);
				Connection newConnection = new Connection(new Weight(randomGen.nextGaussian() / 1251), inputNeuron, outputNeuron);
				inputNeuron.OutputConnections.add(newConnection);
				outputNeuron.InputConnections.add(newConnection);
			}
		}
		for (Neuron neuron : layer3.Neurons)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / 1251);
			Neuron biasNeuron = new Neuron(1);
			neuron.InputConnections.add(new Connection(newWeight, biasNeuron, neuron));
		}
		
		//Layer 3 to layer 4 (output layer)
		for (int i = 0; i < 8; i++)
		{
			Neuron newNeuron = new Neuron();
			outputLayer.Neurons.add(newNeuron);
		}
		for (int i = 0; i < 8; i++)
		{
			for (int j = 0; j < 100; j++)
			{
				Neuron inputNeuron = layer3.Neurons.get(j);
				Neuron outputNeuron = outputLayer.Neurons.get(i);
				Connection newConnection = new Connection(new Weight(randomGen.nextGaussian() / 101), inputNeuron, outputNeuron);
				inputNeuron.OutputConnections.add(newConnection);
				outputNeuron.InputConnections.add(newConnection);
			}
		}
		for (Neuron neuron : outputLayer.Neurons)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / 101);
			Neuron biasNeuron = new Neuron(1);
			neuron.InputConnections.add(new Connection(newWeight, biasNeuron, neuron));
		}
		
		Layers = new ArrayList<Layer>(5);
		
		Layers.add(inputLayer);
		Layers.add(layer1);
		Layers.add(layer2);
		Layers.add(layer3);
		Layers.add(outputLayer);
		this.InputLayer = inputLayer;
		this.OutputLayer = outputLayer;
	}
	*/
	
	
	public void ImageConvolutionNeuralNetwork(int width, int height, int kernelSize, int kernelMovementSpeed, int numOutput, int l1featuremaps, int l2featuremaps)
	{
		Layer inputLayer = new Layer();
		Layer layer1 = new Layer();
		Layer layer2 = new Layer();
		Layer layer3 = new Layer();
		Layer outputLayer = new Layer();
//		int kernelSize = 3;
//		int kernelMovementSpeed = 1;
		
		for (int i = 0; i < width * height; i++)
		{
			Neuron newNeuron = new Neuron();
			inputLayer.Neurons.add(newNeuron);
		}

		//Layer 0 (input layer) to layer 1
		int layer1FeatureMaps = l1featuremaps;
		int layer1FeatureMapWidth = (width - (kernelSize - kernelMovementSpeed)) / kernelMovementSpeed;
		int layer1FeatureMapHeight = (height - (kernelSize - kernelMovementSpeed)) / kernelMovementSpeed;
		for (int i = 0; i < layer1FeatureMaps * layer1FeatureMapWidth * layer1FeatureMapHeight; i++)
		{
			Neuron newNeuron = new Neuron();
			layer1.Neurons.add(newNeuron);
		}
		List<Weight> layer1Weights = new ArrayList<Weight>();
		for (int i = 0; i < (kernelSize * kernelSize + 1) * layer1FeatureMaps; i++)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / (kernelSize * kernelSize + 1));
			layer1Weights.add(newWeight);
		}
		for (int i = 0; i < layer1FeatureMaps; i++)
		{
			for (int j = 0; j < layer1FeatureMapWidth; j++)
			{
				for (int k = 0; k < layer1FeatureMapHeight; k++)
				{
					Neuron outputNeuron = layer1.Neurons.get(i * layer1FeatureMapWidth * layer1FeatureMapHeight + j * layer1FeatureMapHeight + k);
					for (int l = 0; l < (kernelSize * kernelSize); l++)
					{
						Weight weight = layer1Weights.get(i * (kernelSize * kernelSize + 1) + l);
						Neuron inputNeuron = inputLayer.Neurons.get((j * kernelMovementSpeed + l / kernelSize) * width  + (k * kernelMovementSpeed + l % kernelSize));
						Connection newConnection = new Connection(weight, inputNeuron, outputNeuron);
						inputNeuron.OutputConnections.add(newConnection);
						outputNeuron.InputConnections.add(newConnection);
					}
					Weight biasWeight = layer1Weights.get(i * (kernelSize * kernelSize + 1) + kernelSize * kernelSize);
					Neuron biasNeuron = new Neuron(1); 
					outputNeuron.InputConnections.add(new Connection(biasWeight, biasNeuron, outputNeuron));
				}
			}
		}
		layer1Weights.clear();
		
		//Layer 1 to layer 2
		int layer2FeatureMaps = l2featuremaps;
		int layer2FeatureMapWidth = (layer1FeatureMapWidth - (kernelSize - kernelMovementSpeed)) / kernelMovementSpeed;
		int layer2FeatureMapHeight = (layer1FeatureMapHeight - (kernelSize - kernelMovementSpeed)) / kernelMovementSpeed;
		for (int i = 0; i < layer2FeatureMaps * layer2FeatureMapWidth * layer2FeatureMapHeight; i++)
		{
			Neuron newNeuron = new Neuron();
			layer2.Neurons.add(newNeuron);
		}
		List<Weight> layer2Weights = new ArrayList<Weight>();
		for (int i = 0; i < (kernelSize * kernelSize + 1) * layer1FeatureMaps * layer2FeatureMaps; i++)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / (kernelSize * kernelSize * layer1FeatureMaps));
			layer2Weights.add(newWeight);
		}
		for (int i = 0; i < layer2FeatureMaps; i++)
		{
			for (int j = 0; j < layer2FeatureMapWidth; j++)
			{
				for (int k = 0; k < layer2FeatureMapHeight; k++)
				{
					Neuron outputNeuron = layer2.Neurons.get(i * layer2FeatureMapWidth * layer2FeatureMapHeight + j * layer2FeatureMapHeight + k);
					for (int l = 0; l < layer1FeatureMaps; l++)
					{
						for (int m = 0; m < (kernelSize * kernelSize); m++)
						{
							Weight weight = layer2Weights.get(i * (kernelSize * kernelSize + 1) * layer1FeatureMaps + l * (kernelSize * kernelSize + 1) + m);
							Neuron inputNeuron = layer1.Neurons.get(l * layer1FeatureMapWidth * layer1FeatureMapHeight + (j * kernelMovementSpeed + m / kernelSize) * layer1FeatureMapWidth  + (k * kernelMovementSpeed + m % kernelSize));
							Connection newConnection = new Connection(weight, inputNeuron, outputNeuron);
							inputNeuron.OutputConnections.add(newConnection);
							outputNeuron.InputConnections.add(newConnection);
						}
						Weight biasWeight = layer2Weights.get(i * (kernelSize * kernelSize + 1) * layer1FeatureMaps + l * (kernelSize * kernelSize + 1) + layer2FeatureMapWidth * layer2FeatureMapHeight);
						Neuron biasNeuron = new Neuron(1); 
						outputNeuron.InputConnections.add(new Connection(biasWeight, biasNeuron, outputNeuron));
					}
				}
			}
		}
		layer2Weights.clear();
		
		//Layer 2 to layer 3
		int layer3Neurons = numOutput * numOutput;
		for (int i = 0; i < layer3Neurons; i++)
		{
			Neuron newNeuron = new Neuron();
			layer3.Neurons.add(newNeuron);
		}
		for (int i = 0; i < layer3Neurons; i++)
		{
			for (int j = 0; j < layer2FeatureMaps * layer2FeatureMapWidth * layer2FeatureMapHeight; j++)
			{
				Neuron inputNeuron = layer2.Neurons.get(j);
				Neuron outputNeuron = layer3.Neurons.get(i);
				Connection newConnection = new Connection(new Weight(randomGen.nextGaussian() / (layer2FeatureMaps * layer2FeatureMapWidth * layer2FeatureMapHeight)), inputNeuron, outputNeuron);
				inputNeuron.OutputConnections.add(newConnection);
				outputNeuron.InputConnections.add(newConnection);
			}
		}
		for (Neuron neuron : layer3.Neurons)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / (layer2FeatureMaps * layer2FeatureMapWidth * layer2FeatureMapHeight));
			Neuron biasNeuron = new Neuron(1);
			neuron.InputConnections.add(new Connection(newWeight, biasNeuron, neuron));
		}
		
		//Layer 3 to layer 4 (output layer)
		for (int i = 0; i < numOutput; i++)
		{
			Neuron newNeuron = new Neuron();
			outputLayer.Neurons.add(newNeuron);
		}
		for (int i = 0; i < numOutput; i++)
		{
			for (int j = 0; j < layer3Neurons; j++)
			{
				Neuron inputNeuron = layer3.Neurons.get(j);
				Neuron outputNeuron = outputLayer.Neurons.get(i);
				Connection newConnection = new Connection(new Weight(randomGen.nextGaussian() / layer3Neurons), inputNeuron, outputNeuron);
				inputNeuron.OutputConnections.add(newConnection);
				outputNeuron.InputConnections.add(newConnection);
			}
		}
		for (Neuron neuron : outputLayer.Neurons)
		{
			Weight newWeight = new Weight(randomGen.nextGaussian() / layer3Neurons);
			Neuron biasNeuron = new Neuron(1);
			neuron.InputConnections.add(new Connection(newWeight, biasNeuron, neuron));
		}
		
		Layers = new ArrayList<Layer>(5);
		
		Layers.add(inputLayer);
		Layers.add(layer1);
		Layers.add(layer2);
		Layers.add(layer3);
		Layers.add(outputLayer);
		this.InputLayer = inputLayer;
		this.OutputLayer = outputLayer;
	}
	
	

	
	
	public int Import(String file)
	{
		FileInputStream fstream = null;
		DataInputStream in = null;
		BufferedReader br = null;
		try {
			if (new File(file).exists() == true)
			{
				fstream = new FileInputStream(file);
				in = new DataInputStream(fstream);
				br = new BufferedReader(new InputStreamReader(in));
				String strLine;
				strLine = br.readLine();
				Integer numLayers;
				if (strLine.startsWith("#L="))
				{
					numLayers = Integer.parseInt(strLine.substring("#L=".length()));
					Layers = new ArrayList<Layer>(numLayers);
					for (int i = 0; i < numLayers; i++)
						Layers.add(new Layer());
					InputLayer = Layers.get(0);
					OutputLayer = Layers.get(numLayers - 1);
				}
				else
				{
					return -1;
				}
				for (int i = 0; i < numLayers; i++)
				{
					strLine = br.readLine();
					strLine = strLine.replaceFirst("L(\\d)+#N=", "");
					Integer numNeurons = Integer.parseInt(strLine);
					Layer layer = Layers.get(i);
					layer.Neurons = new ArrayList<Neuron>(numNeurons);
					for (int j = 0; j < numNeurons; j++)
					{
						layer.Neurons.add(new Neuron());
					}
				}
				strLine = br.readLine();
				Integer numWeights;
				HashMap<Integer, Weight> weights = new HashMap<Integer, Weight>();
				if (strLine.startsWith("#W="))
				{
					numWeights = Integer.parseInt(strLine.substring("#W=".length()));
				}
				else
				{
					return -1;
				}
				for (int i = 0; i < numWeights; i++)
				{
					strLine = br.readLine();
					strLine = strLine.replaceFirst("W", "");
					String[] pair = strLine.split("=");
					weights.put(Integer.parseInt(pair[0]), new Weight(Double.parseDouble(pair[1])));
				}
				while ((strLine = br.readLine()) != null)   
				{
					String[] weightSplit = strLine.split(":");
					Integer weightIndex = Integer.parseInt(weightSplit[1].replaceFirst("W", ""));
					Weight weight = weights.get(weightIndex);
					String[] conSplit = weightSplit[0].split("-");
					Neuron inNeuron, outNeuron;
					if (conSplit[0].matches("L(\\d)+N(\\d)+"))
					{
						String[] temp = conSplit[0].split("N");
						Integer layerIndex = Integer.parseInt(temp[0].replaceFirst("L", ""));
						Integer neuronIndex = Integer.parseInt(temp[1]);
						inNeuron = Layers.get(layerIndex).Neurons.get(neuronIndex);
					}
					else
					{
						inNeuron = new Neuron(Double.parseDouble(conSplit[0]));
					}
					if (conSplit[1].matches("L(\\d)+N(\\d)+"))
					{						
						String[] temp = conSplit[1].split("N");
						Integer layerIndex = Integer.parseInt(temp[0].replaceFirst("L", ""));
						Integer neuronIndex = Integer.parseInt(temp[1]);
						outNeuron = Layers.get(layerIndex).Neurons.get(neuronIndex);
					}
					else
					{
						outNeuron = new Neuron(Double.parseDouble(conSplit[1]));
					}
					Connection con = new Connection(weight, inNeuron, outNeuron);
					if (inNeuron.OutputConnections != null)
						inNeuron.OutputConnections.add(con);
					if (outNeuron.InputConnections != null)
						outNeuron.InputConnections.add(con);
				}
				return 0;
			}
			else
			{
				return -1;
			}
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return -1;
		}
		catch (IOException ex)
		{
			ex.printStackTrace();
			return -1;
		}
//		catch (Exception ex)
//		{
//			System.err.println("Failed to read input ANN file: " + ex.getMessage());
//			return -1;
//		}
		finally
		{
			try
			{
				fstream.close();
				in.close();
				br.close();
			}
			catch(Exception ex){}
		}
	}
	
	public int Export(String file)
	{
		BufferedWriter out = null;
		try {
			File tempFile = new File(file);
			if (tempFile.exists())
				tempFile.delete();
			FileWriter fstream = new FileWriter(file);
			out = new BufferedWriter(fstream);
			out.write("#L=" + Layers.size());
			out.newLine();
			for (int i = 0; i < Layers.size(); i++)
			{
				out.write("L" + i + "#N=" + Layers.get(i).Neurons.size());
				out.newLine();
			}
			HashMap<Weight, Integer> weights = getWeights();
			out.write("#W=" + weights.size());
			out.newLine();
			Iterator<Entry<Weight, Integer>> weightIterator = weights.entrySet().iterator();
			while (weightIterator.hasNext())
			{
				Entry<Weight, Integer> pair = weightIterator.next();
				out.write("W" + pair.getValue() + "=" + pair.getKey().getWeight());
				out.newLine();
			}
			HashMap<Neuron, String> neuronLabels = getNeuronLabels();
			for (int i = 1; i < Layers.size(); i++)
			{
				Layer layer = Layers.get(i);
				for (int j = 0; j < layer.Neurons.size(); j++)
				{
					Neuron outNeuron = layer.Neurons.get(j);
					String outLabel = neuronLabels.get(outNeuron);
					for (int k = 0; k < outNeuron.InputConnections.size(); k++)
					{
						Connection con = outNeuron.InputConnections.get(k);
						Neuron inNeuron = con.InputNeuron;
						String inLabel = neuronLabels.get(inNeuron);
						if (inLabel == null)
						{
							out.write(((Double)inNeuron.getOuput()).toString());
						}
						else
						{
							out.write(inLabel);
						}
						out.write("-" + outLabel + ":W" + weights.get(con.Weighting).toString());
						out.newLine();
					}
				}
			}
			out.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return -1;
		}
		finally
		{
			try
			{
				out.close();
			}
			catch(Exception ex){}
		}
		return 0;
	}
	
	private HashMap<Weight, Integer> getWeights()
	{
		HashMap<Weight, Integer> weights = new HashMap<Weight, Integer>();
		Integer index = 0;
		for (int i = 0; i < Layers.size(); i++)
		{
			Layer layer = Layers.get(i);

			Integer numWeightCount = 0;
			for (int j = 0; j < layer.Neurons.size(); j++)
			{
				Neuron neuron = layer.Neurons.get(j);
				for (int k = 0; k < neuron.InputConnections.size(); k++)
				{
					Connection con = neuron.InputConnections.get(k);
//					if (weights.containsKey(con.Weighting) == false)
//					{
//						weights.put(con.Weighting, index++);
//					}
					Integer temp = weights.get(con.Weighting);
					if (temp == null)
					{
						weights.put(con.Weighting, index++);
						numWeightCount++;
					}
				}
			}
			
//			System.out.println(numWeightCount);
		}
		return weights;
	}
	
	public void ClearDiagHessian()
	{
		HashMap<Weight, Integer> weights = getWeights();
		Set<Weight> weightSet = weights.keySet();
		Iterator<Weight> iterator = weightSet.iterator();
		while (iterator.hasNext())
		{
			Weight weight = iterator.next();
			weight.setDiagHessian(0);
		}
	}
	
	private HashMap<Neuron, String> getNeuronLabels()
	{
		HashMap<Neuron, String> neuronLabels = new HashMap<Neuron, String>();
		for (int i = 0; i < Layers.size(); i++)
		{
			Layer layer = Layers.get(i);
			for (int j = 0; j < layer.Neurons.size(); j++)
			{
				Neuron neuron = layer.Neurons.get(j);
				neuronLabels.put(neuron, "L" + i + "N" + j);
			}
		}
		return neuronLabels;
	}
}
